﻿//#include "数组主接口标头.h"
//#include "小游戏标头.h"

// 数组是一组相同元素的集合
// 数组的声明格式：数据类型 数组名[数组大小(常量或常量表达式)];
// C99标准引入了一种新的声明方式，允许在声明时指定数组大小为变量
// 数组在内存中是连续存储的，数组名就是数组的首元素的地址
// 数组的下标从 0 开始，最大下标为数组大小减 1
// 数组的元素可以是任意数据类型，包括整型、浮点型、字符型、结构体、共用体等
// 数组的大小可以是常量或变量，但不能为负数
// 数组的初始化可以是常量、变量、表达式、函数调用等，但不能为数组元素的地址或指针

#include "common.h"


// 打印数组函数
void print_array(void** arr, const char* type, size_t size) {
    // 类型转换，避免警告
    if (strcmp(type, "int") == 0) {
        int* int_arr = (int*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%d  ", int_arr[i]);
    }
	else if (strcmp(type, "float") == 0) {
        float* float_arr = (float*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%f  ", float_arr[i]);
    }
	else if (strcmp(type, "char") == 0) {
        char* char_arr = (char*)*arr;
        for (int i = 0; i < size; ++i)
			printf("%c  ", char_arr[i]);
    }
	else if (strcmp(type, "double") == 0) {
        double* double_arr = (double*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%lf  ", double_arr[i]);
    }
	else if (strcmp(type, "long double") == 0) {
        long double* long_double_arr = (long double*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%llf  ", long_double_arr[i]);
	}
	else if (strcmp(type, "long") == 0) {
        long* long_arr = (long*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%ld  ", long_arr[i]);
    }
	else if (strcmp(type, "short") == 0) {
        short* short_arr = (short*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%hd  ", short_arr[i]);
    }
	else if (strcmp(type, "long long") == 0) {
        long long* long_long_arr = (long long*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%lld  ", long_long_arr[i]);
    }
	else if (strcmp(type, "unsigned int") == 0) {
        unsigned int* unsigned_int_arr = (unsigned int*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%u  ", unsigned_int_arr[i]);
    }
	else if (strcmp(type, "unsigned char") == 0) {
        unsigned char* unsigned_char_arr = (unsigned char*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%hhu  ", unsigned_char_arr[i]);
    }
	else if (strcmp(type, "unsigned long") == 0) {
        unsigned long* unsigned_long_arr = (unsigned long*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%lu  ", unsigned_long_arr[i]);
    }
	else if (strcmp(type, "unsigned short") == 0) {
        unsigned short* unsigned_short_arr = (unsigned short*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%hu  ", unsigned_short_arr[i]);
    }
	else if (strcmp(type, "unsigned long long") == 0) {
        unsigned long long* unsigned_long_long_arr = (unsigned long long*)*arr;
        for (int i = 0; i < size; ++i)
            printf("%llu  ", unsigned_long_long_arr[i]);
    }
	else
        printf("未知类型");
	printf("\n");
}


int main()
{
	int i = 0;
	// 声明数组
	{
		print_start_line("数组声明部分开始");
		printf("Size of long double: %zu bytes\n", sizeof(long double));
		int arr1[2 + 3] = { 1, 2, 3, 4, 5 }; // 大小为5，并初始化元素
		// 以下不完全初始化，剩余元素用 0 填充
		char str1[10] = { 'h', 'e', 'l', 'l', 'o' };
		// 上述数组的元素为 {'h', 'e', 'l', 'l', 'o', 0, 0, 0, 0, 0}
		char str2[15] = "hello\0me"; // 使用上基本等价于上一行，元素不相同
		// 上数组的元素为 {'h', 'e', 'l', 'l', 'o', '\0', 0, 0, 0, 0}
		float flt[] = { 1.2f, 3.4f, 5.6f };
		// 完全初始化，元素为 {1.2, 3.4, 5.6}
		/* 以下格式只有在C99之后才支持，注意 VS 2022 还不支持
		int n = 2 + 3;
		int arr[n]; // 数组大小为变量，不能初始化元素	*/
		//说明一点：数组的类型为去掉名字剩下的，比如第一个声明类型为 int[5]
		// 打印数组时，确保指针类型正确，否则会出现内存访问错误
		const void* arr_ptr = (void*)arr1; // 指针类型为 void*
		print_array((void**)&arr_ptr, "int", sizeof(arr1) / sizeof(arr1[0])); // 打印数组

		arr_ptr = (void*)str1; // 指针类型为 void*
		print_array((void**)&arr_ptr, "char", sizeof(str1) / sizeof(str1[0])); // 打印数组

		arr_ptr = (void*)str2; // 指针类型为 void*
		print_array((void**)&arr_ptr, "char", sizeof(str2) / sizeof(str2[0])); // 打印数组，计算出来的第三个参数是 10

		arr_ptr = (void*)flt; // 指针类型为 void*
		print_array((void**)&arr_ptr, "float", sizeof(flt) / sizeof(flt[0])); // 打印数组
		
		print_end_line("数组声明部分结束");
	}

	// 测试数组在内存中的布局
	{
		print_start_line("测试数组在内存中的布局部分开始");
		int arr2[3] = { 1, 2, 3 };
		int arr2_size = sizeof(arr2) / sizeof(arr2[0]); // 数组大小
		for (i = 0; i < arr2_size; ++i)
		{ // 注意数组的下标从 0 开始，内存地址是 16 进制的
			printf("arr2[%d]的地址 = %p ", i, &arr2[i]);
		} // 可以看到数组的元素地址是连续的
		printf("\n");

		print_end_line("测试数组在内存中的布局部分结束");
	}

	// 二维数组
	{
		print_start_line("二维数组部分开始");
		int arr3[2][3] = { {1, 2, 3}, {4, 5, 6} }; // 声明二维数组
		// 以上声明的二位数组可以写为：{ 1, 2, 3, 4, 5, 6 }; -> 不推荐
		int arr4[][5] = { {1, 2, 3, 4, 5}, {4} };
		// 以上数组的元素为 {{1, 2, 3, 4, 5}, {4, 0, 0, 0, 0}};
		/* 二维数组的行数可以省略，但不能省略列数和列数，否则会出错 */
		int arr4_size = sizeof(arr4) / sizeof(arr4[0]); // 数组大小（行数）
		int arr4_col_size = sizeof(arr4[0]) / sizeof(arr4[0][0]); // 列
		for (i = 0; i < arr4_size; ++i)
		{ // 遍历二维数组的每一行，每一列
			for (int j = 0; j < arr4_col_size; ++j)
				printf("arr3[%d][%d] = %d\t", i, j, arr4[i][j]);
			printf("\n");
		}
		//多维数组也是同样的道理，通过下标访问修改元素
		// 二位数组的元素地址是连续的，但不是二维数组的元素地址
		for (i = 0; i < arr4_size; i++)
		{ // 遍历二维数组的每一行，每一列
			for (int j = 0; j < arr4_col_size; j++)
				printf("arr4[%d][%d]的地址 = %p\t", i, j, &arr4[i][j]);
			printf("\n");
		}

		print_end_line("二维数组部分结束");
	}

	// 数组的越界
	{
		print_start_line("数组的越界部分开始");
		//当数组下标超出范围时，可能会导致程序崩溃，因此需要注意数组的边界检查
		int arr5[3] = { 1, 2, 3 };
		/* for (i = 0; i < 5; ++i)
			printf("arr5[%d] = %d\t", i, arr5[i]); // 超出后值会被打印
			// 数组下标超出范围，程序会出现无法估计的问题
		printf("\n"); */
		int arr6[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
		for (i = 0; i < 3; ++i)
			for (int j = 0; j < 4; ++j)
				printf("arr6[%d][%d] = %d\t", i, j, arr6[i][j]);
		// 超出后值会被打印（注意每次循环的范围）
		printf("\n");

		print_end_line("数组的越界部分结束");
	}

	// 排序算法
	{
		print_start_line("排序算法部分开始");
		int arr7[10] = { 5, 3, 8, 6, 2, 7, 1, 4, 9, 0 };
		bubble_sort(arr7, 10); // 冒泡排序
		print_int_array(arr7, 10); // 打印排序后的数组

		int arr8[10] = { 5, 3, 8, 4, 6, 2, 0, 7, 1, 9 };
		selection_sort(arr8, 10); // 选择排序
		print_int_array(arr8, 10); // 打印排序后的数组

		int arr9[10] = { 8, 5, 3, 4, 6, 2, 7, 1, 9, 0 };
		insertion_sort(arr9, 10); // 插入排序
		print_int_array(arr9, 10); // 打印排序后的数组

		int arr10[10] = { 5, 3, 8, 7, 4, 9, 0, 6, 2, 1 };
		quick_sort(arr10, 0, 9); // 快速排序
		print_int_array(arr10, 10); // 打印排序后的数组

		int arr11[10] = { 5, 3, 7, 6, 8, 4, 1, 9, 0, 2 };
		merge_sort(arr11, 0, 9); // 归并排序
		print_int_array(arr11, 10); // 打印排序后的数组

		print_end_line("排序算法部分结束");
	}

	// 数组名的理解
	{
		print_start_line("数组名的理解部分开始");
		//数组名确实能表示首元素的地址
		//但是有2个例外:
		//1. sizeof(数组名)，这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
		//2. &数组名，这里的数组名表示整个数组,取出的是整个数组的地址
		int arr12[10] = { 0 };
		int arr13[3][4] = { 0 };
		printf("%zd\t%zd\n", sizeof(arr12), sizeof(arr13));
		printf("----------------------\n");
		printf("%p\t%p\n", arr12, arr13);// arr12/arr13 就是首元素的地址
		printf("%p\t%p\n", arr12 + 1, arr13 + 1);
		// 这里的指针变量加 1，指向下一个同类元素的地址（比如一个 int 是 4）
		printf("----------------------\n");
		printf("%p\t%p\n", &arr12[0], &arr13[0]);// 首元素的地址
		printf("%p\t%p\n", &arr12[0] + 1, &arr13[0] + 1);
		printf("----------------------\n");
		printf("%p\t%p\n", &arr12, &arr13);// 数组的地址，也是首元素的地址
		printf("%p\t%p\n", &arr12 + 1, &arr13 + 1);
		// 数组的地址加 1，指向整个数组的下一个元素的地址

		print_end_line("数组名的理解部分开始");
	}
	
	// 数组实现小游戏
	{
		// 三子棋游戏
		//sudoku_start();

		// 扫雷游戏
		//sweep_start();
	}

	return 0;
}

// 特别的类型 long double 说明
/*
根据不同的编译器和操作系统，long double 的内存占用可能有以下几种常见情况：

1. 80位浮点数（扩展精度浮点数）
在许多 x86 和 x86_64 架构（例如 Intel 和 AMD 的处理器）上，long double 通常对应于 x87 FPU（浮点单元）中的 80位浮点数。
在这种情况下，long double 占用 10个字节（80位 ÷ 8 = 10）。
示例平台：Windows、Linux、macOS（运行在 x86 或 x86_64 硬件上）。
64位浮点数（与 double 相同）

2. 在一些平台上，long double 与 double 是相同的，都占用 8个字节（64位）。
示例编译器和平台：GNU GCC 编译器在某些平台上（例如 ARM 架构）可能将 long double 和 double 视为等价类型。
128位浮点数（IEEE 754 的可选扩展精度）

3. 在某些平台和编译器中，long double 可能使用 IEEE 754 的 128位浮点数（也称为 quad-precision 浮点数）。
在这种情况下，long double 占用 16个字节（128位 ÷ 8 = 16）。
示例平台：某些支持 IEEE 754 扩展精度的系统。
*/




